#include "util.h"

int fill_rand(matrix *A) {

    /* Make sure that the random number generator is initialized first */

    for (int i=0; i<(A->row); i++) 
        for (int j=0; j<(A->col); j++) 
            MAT_ELEM(A,i,j) =  (double)rand()/(double)RAND_MAX;
      
    return _ESUCCESS;
}

int fill_zero(matrix *A) {

    for (int i=0; i<(A->row); i++) 
        for (int j=0; j<(A->col); j++) 
            MAT_ELEM(A,i,j) = 0.0;

    return _ESUCCESS;
}

int fill_ident(matrix *A) {

    if (A->row != A->col) 
        return _EBADSIZE;

    fill_zero(A);

    for (int i=0; i<(A->row); i++) 
        MAT_ELEM(A,i,i) = 1.0;

    return _ESUCCESS;
}

int read_mat(FILE *F, matrix *A) {
    /* Reads the formatted matrix from a file stream 
       Format expected is:
       First line contains two integers, specifying the size (row and col)
       followed by the actual matrix in row major format

       Delimiters are ' ' and '\n'
    */

    int rv, row, col, cnt=0;
    rewind(F);

    rv = fscanf(F, "%d", &row);
    rv += fscanf(F, "%d", &col);

    if (rv != 2)
        return _EBADSIZE;

    rv = init_mat(A, row, col);

    if (rv != _ESUCCESS)
        return rv;

    for (int i=0; i<row; i++) {
        for (int j=0; j<col; j++) {
            cnt += fscanf(F, "%lf", &(MAT_ELEM(A,i,j)));
        }
    }   

    if (cnt != row*col)
        return _EBADSIZE;

    return _ESUCCESS;
}


int load_mat(FILE *F, matrix *A) {

    /* Here is the format that we expect:
       |Rowcount|Colcount|major|data ...|*/

    int rv, totalsize, row, col, major, cnt;

    rewind(F);

    /* Let's find the total size of the file first */
    fseek(F, 0L, SEEK_END);
    totalsize = ftell(F);

    rewind(F);
   
    /* Set up parameters in the matrix data structure */
    rv = fread(&row, sizeof(A->row), 1, F);
    rv += fread(&col, sizeof(A->col), 1, F);
    rv += fread(&major, sizeof(A->major), 1, F);
 
    if (rv != 3)
        return _EBADSIZE;

    rv = init_mat(A, row, col);
   
    if (rv != _ESUCCESS) 
        return rv;

    A->major = major;

#ifdef ASSERT
    assert(totalsize == sizeof(double)*(A->row)*(A->col) + sizeof(A->row) 
                                       + sizeof(A->col) + sizeof(A->major));
#endif
    
    cnt = fread(A->data, sizeof(double), A->row * A->col, F);
    if (cnt != A->row * A->col)
        return _EBADSIZE;



    return _ESUCCESS;
}

int print_mat(FILE *F, matrix *A) {

    fprintf(F, "%d %d\n", A->row, A->col);

    /* Delimiters are ' ' and '\n' */
    for (int i=0; i<(A->row); i++) {
        for (int j=0; j<(A->col); j++) 
            fprintf(F, "%lf ", MAT_ELEM(A,i,j));
        fprintf(F, "\n");
    }
    fflush(F);

    return _ESUCCESS;
}

int dump_mat(FILE *F, matrix *A) {

    int cnt;

    rewind(F);

    /* Set up parameters in the matrix data structure */
    fwrite(&(A->row), sizeof(A->row), 1, F);
    fwrite(&(A->col), sizeof(A->col), 1, F);
    fwrite(&(A->major), sizeof(A->major), 1, F);
    
    cnt = fwrite(A->data, sizeof(double), A->row * A->col, F);
    if (cnt != A->row * A->col)
        return _EBADSIZE;

    fflush(F);

    return _ESUCCESS;
}


/* Horizontal concatenation of matrices */
int concat_mat_hor(matrix *A, matrix *B, matrix *res) {

    /* returns [A B] */

    int rv;
    if (A->row != B->row)
        return _EBADSIZE;

    rv = init_mat(res, A->row, A->col + B->col); 

    if (rv != _ESUCCESS) 
        return rv;
   
    for (int i=0; i<A->row; i++) {
        for (int j=0; j<(A->col + B->col); j++) {
            MAT_ELEM(res,i,j) = j < (A->col) ? MAT_ELEM(A,i,j) : MAT_ELEM(B,i,j-(A->col));
        }
    }

    return _ESUCCESS;
}

/* Vertical concatenation of matrices */
int concat_mat_ver(matrix *A, matrix *B, matrix *res) {

    /* returns [A; B] */

    int rv;
    if (A->col != B->col)
        return _EBADSIZE;

    rv = init_mat(res, A->row + B->row, A->col); 

    if (rv != _ESUCCESS) 
        return rv;
   
    for (int i=0; i<(A->row + B->row); i++) {
        for (int j=0; j< A->col; j++) {
            MAT_ELEM(res,i,j) = i < (A->row) ? MAT_ELEM(A,i,j) : MAT_ELEM(B,i-(A->row),j);
        }
    }

    return _ESUCCESS;
}

/* gives submatrix A (start_row:end_row, start_col:end_col) 
   including both the start and end stuff
*/
int submatrix(matrix *A, int start_row, int end_row, int start_col, int end_col, matrix *res) {

    int rv = 0;

    if (start_row>end_row   || start_col>end_col    || 
            start_row<0     || start_row>= A->row   ||
            end_row<0       || end_row>=A->row      ||
            start_col<0     || start_col>= A->col   ||
            end_col<0       || end_col>=A->col )
        return _EBADSIZE;

    rv = init_mat(res, end_row-start_row+1, end_col-start_col+1);

    if (rv != _ESUCCESS)
        return rv;

    for (int i=0; i<(end_row-start_row+1); i++) {
        for (int j=0; j<(end_col-start_col+1); j++) 
            MAT_ELEM(res,i,j) = MAT_ELEM(A,start_row+i,start_col+j);
    }

    return _ESUCCESS;
}


/* Compare matrices */
int mat_equal(matrix *A, matrix *B) {

    double epsilon = 1e-6;

    if (!(A->row == B->row && A->col == B->col))
        return _EBADSIZE;

    for (int i=0; i<(A->row); i++) {
        for (int j=0; j<(A->col); j++) 
            if (fabs(MAT_ELEM(A,i,j)-MAT_ELEM(B,i,j)) > epsilon) 
                return 0;
    }

    return 1;
}

/* Copies a matrix A to another matrix B */
int copy_mat(matrix *A, matrix *B) {

    int rv;

    rv = init_mat(B, A->row, A->col);
    if (rv != _ESUCCESS)
        return rv;

    B->major = A->major;

    for (int i=0; i<(A->row); i++) 
        for (int j=0; j<(A->col); j++) 
            MAT_ELEM(B,i,j) = MAT_ELEM(A,i,j);

    return _ESUCCESS;
}

/* Calculates a rudimentary hash of a matrix data */
int hash_mat(matrix *A, double *hash) {

    *hash = 0.0;
    for (int i=0; i<(A->row); i++) 
        for (int j=0; j<(A->col); j++) 
            *hash += MAT_ELEM(A,i,j);
    
    *hash += A->row;
    *hash += A->col;

    return _ESUCCESS;

}

double read_timer( )
{
    struct timeval t;
    gettimeofday( &t, NULL );
    return (double) ((t.tv_sec) + 1.0e-6*t.tv_usec);
}

